package crud

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"cms/system/cmd"
	"cms/system/helper"
	"github.com/fatih/color"
	"github.com/go-ini/ini"
	_ "github.com/go-sql-driver/mysql"
	"xorm.io/xorm"
)

const DS = string(filepath.Separator)

var (
	join = helper.Join
)

func New(p *cmd.CmdParam) *cGlobal {
	g := new(cGlobal)
	color.Green("即将开始初始化...")
	g.Init(p)
	color.Green("即将初始化完成.")
	g.SaveData()
	color.Green("向菜单表里写入数据完成.")
	return g
}

func pathJoin(p ...string) string {
	return join(DS, p...)
}

func (g *cGlobal) Init(p *cmd.CmdParam) {
	rootPath := p.Path // 生成项目的根路径
	table := p.Table   // 表名
	Group := p.Group   // 模块名称，默认为 admin

	publicPath := pathJoin(rootPath, "public") // public路径
	htmlPath := pathJoin(publicPath, Group)
	JsPath := pathJoin(publicPath, "static", Group, "js")
	JsURLPath := "/" + join("/", "static", Group, "js", table) + ".js"

	if strings.Index(table, "_") > 0 {
		htmlPath += DS + strings.ReplaceAll(table, "_", DS)
	} else {
		htmlPath += table
	}

	g.Name = p.Name
	g.RootPath = rootPath
	g.HtmlURI = p.HTMLUri
	g.JsURI = p.JsUri
	g.JsURLPath = JsURLPath
	g.TableName = table
	g.ModuleName = Group

	g.AppPath = &AppPath{
		ModelPath:      p.AppPath.Model,
		ValidatePath:   p.AppPath.Validate,
		ControllerPath: p.AppPath.Controller,
		ServicePath:    p.AppPath.Service,
		JsPath:         JsPath,
		HtmlPath:       htmlPath,
	}

	g.AppFiles = &AppFiles{
		Controller:   rootPath + DS + p.AppClass.Controller + ".php",
		ValidateAdd:  rootPath + DS + p.AppClass.ValidateAdd + ".php",
		ValidateEdit: rootPath + DS + p.AppClass.ValidateEdit + ".php",
		Service:      rootPath + DS + p.AppClass.Service + ".php",
		Model:        rootPath + DS + p.AppClass.Model + ".php",
		Js:           JsPath + DS + table + ".js",
		HtmlIndex:    htmlPath + DS + "index.html",
		HtmlAdd:      htmlPath + DS + "add.html",
		HtmlEdit:     htmlPath + DS + "edit.html",
	}

	g.AppClass = &AppClass{
		Controller:   p.AppClass.Controller,
		Service:      p.AppClass.Service,
		Model:        p.AppClass.Model,
		ValidateAdd:  p.AppClass.ValidateAdd,
		ValidateEdit: p.AppClass.ValidateEdit,
	}

	g.NameSpace = &NameSpace{
		Controller: p.NameSpace.Controller,
		Validate:   p.NameSpace.Validate,
		Service:    p.NameSpace.Service,
		Model:      p.NameSpace.Model,
	}

	cinx := strings.LastIndex(p.AppClass.Controller, DS)
	g.Class = p.AppClass.Controller[cinx+1:]
	g.Suffix = p.Suffix
	g.Ds = DS
	g.Field = p.Field
	g.Pid = p.Pid
	g.HasModel = p.HasModel
	g.OnlyIndex = p.OnlyIndex
	g.TableSchema = g.parserTable()
}

func (g *cGlobal) SaveData() int64 {
	mMap := g.getDbCfg()
	prefix := mMap["prefix"]

	db, err := g.getDb()
	if err != nil {
		helper.Dry(err.Error())
	}
	curtime := time.Now().Format("2006-01-02 15:04:05")
	name := g.Name

	// 先查找主菜单是否存在
	hasExists, err := g.exists(db, prefix+"admin_menu", cmd.MapStr{"name": name})
	if err != nil {
		helper.Dry(err.Error())
	}

	if hasExists {
		return 0
	}
	sql := ""
	var lastid int64 = 0
	if g.Pid > 0 {
		lastid = int64(g.Pid)
	} else {
		sql = fmt.Sprintf("INSERT INTO `%vadmin_menu`(`name`, `href`, `rule`, `is_menu`, `sort`, `pid`, `is_show`, `icon`, `des`, `create_time`, `update_time`) VALUES ('%v', '%vindex.html', '', 1, 0, 1, 1, 'layui-icon layui-icon-username', '%v', '%v', '%v');", prefix, name, g.HtmlURI, name, curtime, curtime)

		lastid, err = g.insert(db, sql)
		if err != nil {
			helper.Dry("插入主菜单数据失败: " + err.Error())
		}
	}
	if name == "" {
		name = "搜索列表"
	}

	sql = fmt.Sprintf("INSERT INTO `%vadmin_menu`(`name`, `href`, `rule`, `is_menu`, `sort`, `pid`, `is_show`, `icon`, `des`, `create_time`, `update_time`) VALUES ('%s', '%vindex.html', '%vindex', 1, 0, %v, 1, 'layui-icon layui-icon-username', '%s', '%v', '%v');", prefix, name, g.HtmlURI, g.JsURI, lastid, name, curtime, curtime)

	selfid, err := g.insert(db, sql)
	if err != nil {
		helper.Dry("插入菜单数据失败: " + err.Error())
	}
	if !g.OnlyIndex {
		sql = fmt.Sprintf("INSERT INTO `%vadmin_menu`(`name`, `href`, `rule`, `is_menu`, `sort`, `pid`, `is_show`, `icon`, `des`, `create_time`, `update_time`) VALUES ('添加', '', '%vadd', 2, 0, %v, 1, 'layui-icon layui-icon-username', '添加', '%v', '%v');", prefix, g.JsURI, selfid, curtime, curtime)
		_, _ = g.insert(db, sql)
		if err != nil {
			helper.Dry("插入菜单数据失败: " + err.Error())
		}

		sql = fmt.Sprintf("INSERT INTO `%vadmin_menu`(`name`, `href`, `rule`, `is_menu`, `sort`, `pid`, `is_show`, `icon`, `des`, `create_time`, `update_time`) VALUES ('编辑', '', '%vedit', 2, 0, %v, 1, 'layui-icon layui-icon-username', '编辑', '%v', '%v');", prefix, g.JsURI, selfid, curtime, curtime)
		_, err = g.insert(db, sql)
		if err != nil {
			helper.Dry("插入菜单数据失败: " + err.Error())
		}
	}

	sql = fmt.Sprintf("INSERT INTO `%vadmin_menu`(`name`, `href`, `rule`, `is_menu`, `sort`, `pid`, `is_show`, `icon`, `des`, `create_time`, `update_time`) VALUES ('删除', '', '%vdel', 2, 0, %v, 1, 'layui-icon layui-icon-username', '删除', '%v', '%v');", prefix, g.JsURI, selfid, curtime, curtime)
	_, err = g.insert(db, sql)
	if err != nil {
		helper.Dry("插入主菜单数据失败: " + err.Error())
	}

	sql = fmt.Sprintf("INSERT INTO `%vadmin_menu`(`name`, `href`, `rule`, `is_menu`, `sort`, `pid`, `is_show`, `icon`, `des`, `create_time`, `update_time`) VALUES ('详情', '', '%vinfo', 2, 0, %v, 1, 'layui-icon layui-icon-username', '详情', '%v', '%v');", prefix, g.JsURI, selfid, curtime, curtime)
	_, err = g.insert(db, sql)
	if err != nil {
		helper.Dry("插入主菜单数据失败: " + err.Error())
	}

	return lastid
}

func (g *cGlobal) insert(db *xorm.Engine, sql string) (int64, error) {
	res, err := db.Exec(sql)
	if err != nil {
		helper.Dry("执行sql失败，原因：" + err.Error())
	}

	return res.LastInsertId()
}

func (g *cGlobal) exists(db *xorm.Engine, table string, where cmd.MapStr) (bool, error) {
	condition := make([]string, 0)
	for k, v := range where {
		condition = append(condition, fmt.Sprintf("`%v` = '%v'", k, v))
	}

	conditionStr := join(" and ", condition...)

	sql := fmt.Sprintf("select exists(select 1 from %v where %v) as _exists_", table, conditionStr)
	res, err := db.QueryString(sql)
	if err != nil {
		return false, err
	}

	return res[0]["_exists_"] == "1", nil
}

func (g *cGlobal) getDbCfg() cmd.MapStr {
	return parseIni(g.RootPath + g.Ds + ".env")
}

func (g *cGlobal) getDb() (*xorm.Engine, error) {
	mMap := g.getDbCfg()

	hostName := mMap["hostname"]
	userName := mMap["username"]
	dataBase := mMap["database"]
	passWord := mMap["password"]
	hostPort := mMap["hostport"]
	charSet := mMap["charset"]

	dbStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s", userName,
		passWord,
		hostName,
		hostPort,
		dataBase,
		charSet)
	db, err := xorm.NewEngine("mysql", dbStr)
	if err != nil {
		return nil, fmt.Errorf("连接数据库失败：%v", err.Error())
	}

	return db, nil
}

func (g *cGlobal) parserTable() []cmd.MapStr {
	mMap := g.getDbCfg()
	prefix := mMap["prefix"]
	dataBase := mMap["database"]

	db, err := g.getDb()
	if err != nil {
		helper.Dry(err.Error())
	}

	sql := fmt.Sprintf(`SELECT
		DATA_TYPE as 'ctype',
		CHARACTER_MAXIMUM_LENGTH as 'length',
		NUMERIC_PRECISION as 'intlength',
    	COLUMN_NAME as 'column',
    	COLUMN_COMMENT as 'comment'
	FROM
    	INFORMATION_SCHEMA.COLUMNS
	WHERE TABLE_SCHEMA = '%v' AND TABLE_NAME = '%v'`, dataBase, prefix+g.TableName)

	res, err := db.QueryString(sql)

	if err != nil {
		helper.Dry("无法获取数据表结构：" + err.Error())
	}

	return res
}

func parseIni(iniPath string) cmd.MapStr {
	cfg, err := ini.LoadSources(ini.LoadOptions{SkipUnrecognizableLines: true}, iniPath)
	if err != nil {
		fmt.Println("ini文件不存在")
		os.Exit(0)
	}

	app, err := cfg.GetSection("APP")
	iniMap := make(cmd.MapStr, 0)
	if err != nil {
		iniMap["default_timezone"] = "Asia/Shanghai"
	} else {
		for _, key := range app.Keys() {
			iniMap[strings.ToLower(key.Name())] = key.Value()
		}
	}

	db, err := cfg.GetSection("DATABASE")
	if err != nil {
		fmt.Println("ini文件中没有配置数据库项")
		os.Exit(0)
	}

	for _, key := range db.Keys() {
		iniMap[strings.ToLower(key.Name())] = key.Value()
	}

	return iniMap
}
